# Reksio - Memory Map Editor
# Copyright (C) 2023 CERN
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# In applying this licence, CERN does not waive the privileges and immunities
# granted to it by virtue of its status as an Intergovernmental Organization or
# submit itself to any jurisdiction.

import reksio
import sys
import os
import logging
from pathlib import Path
from collections.abc import MutableMapping
from abc import abstractmethod
import configparser
import traceback

from expand_submap import lookup


class Argument:
    def __init__(self, name, description, default_value, data_type=None, enum=None):
        self.name = name
        self.description = description
        self.default_value = default_value
        if data_type is None:
            self.data_type = str
        else:
            self.data_type = data_type
        self.enum = enum

    def __repr__(self):
        return f"{self.name}, {self.description}, {self.default_value}, {self.data_type}"


class Settings(MutableMapping):
    def __init__(self, filename, section, arguments, save=True, variables=None):
        self.filename = filename
        self.section = section
        self.config = configparser.ConfigParser(allow_no_value=True, interpolation=configparser.ExtendedInterpolation())
        self.arguments = arguments
        if variables is None:
            variables = {}
        self.variables = variables

        if os.path.exists(self.filename):
            self.config.read(self.filename)
        self.fill_config()
        if save:
            self.save()

    def save(self):
        with open(self.filename, 'w') as f:
            self.config.write(f)

    def fill_config(self):
        if self.section not in self.config:
            self.config.add_section(self.section)
        for argument in self.arguments:
            if not self.config.has_option(self.section, argument.name):
                # self.config.set(self.generator_section, f"; {argument.description}")
                self.config.set(self.section, argument.name, argument.default_value)

    def __len__(self):
        return len(self.config.options(self.section))

    def __getitem__(self, item):
        try:
            # return proper type
            data_type = [arg.data_type for arg in self.arguments if arg.name == item]
            if len(data_type) == 1:
                fn_type = {
                    bool: self.config.getboolean,
                    str: self.config.get,
                    float: self.config.getfloat,
                    int: self.config.getint
                }
                return fn_type.get(data_type[0], self.config.get)(self.section, item, vars=self.variables)
            return self.config.get(self.section, item, vars=self.variables)
        except configparser.NoOptionError:
            raise KeyError(f"Key {item} missing in the settings file!")

    def __setitem__(self, key, value):
        if self.config.has_option(self.section, key):
            self.config.set(self.section, key, value)
        else:
            raise KeyError(f"Key {key} is not allowed attribute!")

    def __delitem__(self, key):
        if self.config.has_option(self.section, key):
            self.config.remove_option(self.section, key)

    def __iter__(self):
        return iter(self.config.options(self.section))

    def __str__(self):
        res = ""
        for item in self:
            res += f"{item} = {self[item]}\n"
        return res


class Generator:
    CONFIG_PATH = ""
    GENERATOR_NAME = ""

    def __init__(self, main_window, submap_node=None):
        # variables that can be used by the settings
        self.variables = {}

        self.main_window = main_window

        if not main_window.hasFile():
            raise Exception("Memory map is not saved")

        if main_window.isModified():
            reksio.popup_warning("Save before use!", "Please save your map before running generation tools!")
            raise Exception("Please save your map before running generation tools!")

        nodes_model = self.main_window.getNodesModel()
        if not nodes_model:
            raise Exception("No nodes model present!")
        root_node = nodes_model.getRoot()
        top_item = root_node.getChildByType("memory-map")
        if not top_item:
            raise Exception("No memory-map node found in your memory map!")
        self.root_opened_file = str(main_window.currentFile)
        self.root_opened_dir = os.path.dirname(self.root_opened_file)
        self.root_settings_file = self.get_settings_file(self.root_opened_file)
        self.root_settings = Settings(self.root_settings_file,
                                      self.CONFIG_PATH,
                                      self.get_arguments(),
                                      variables=self.variables)

        self.submap_node = submap_node
        self.top_item = top_item

        if submap_node is None:
            # operating on root map
            self.opened_file = self.root_opened_file
            self.opened_dir = self.root_opened_dir
            self.map_node = top_item
            self.settings_file = self.root_settings_file
            self.settings = self.root_settings
        else:
            # submap version
            submap_filename = self.get_submap_filename(submap_node)
            if submap_filename is None:
                raise Exception("Tried to run a generator on a submap without a filename!")
            self.opened_file = submap_filename

            map_node = self.submap_node.getChildByType("memory-map")
            if map_node is None:
                raise Exception(f"Submap {self.submap_node} doesn't have memory-map child!")

            self.opened_dir = os.path.dirname(self.opened_file)
            self.map_node = map_node
            self.settings_file = self.get_settings_file(self.opened_file)
            self.settings = Settings(self.settings_file,
                                     self.CONFIG_PATH,
                                     self.get_arguments(),
                                     variables=self.variables)

        output_path = Path(self.settings.get('output_path', ''))

        if output_path.is_absolute():
            raise Exception(f"Output path set inside {self.settings_file} must be relative!")

        self.output_path = str(Path(self.opened_dir) / output_path)

        # add node name to the variables
        self.variables['NODE_NAME'] = self.map_node.name
        # add x-hdl/name-suffix
        name_suffix_attr = self.map_node.getAttribute(['x-hdl', 'name-suffix'])
        self.variables['HDL_NAME_SUFFIX'] = '' if name_suffix_attr is None else name_suffix_attr.value

    def is_generation_skipped(self):
        return self.settings.config.getboolean(self.CONFIG_PATH, 'skip', fallback=False)

    def get_submap_nodes(self):
        if self.submap_node is not None:
            return lookup(self.submap_node, "submap")
        else:
            return lookup(self.top_item, "submap")

    @staticmethod
    def get_submap_filename(submap):
        if not submap.getAttribute("filename"):
            return None
        computed_path_attr = submap.getAttribute(["computed", "file_path"])
        if computed_path_attr is None:
            return None
        computed_path = computed_path_attr.value
        if os.path.exists(computed_path):
            return computed_path

    @staticmethod
    def get_settings_file(map_file):
        filename, _ = os.path.splitext(map_file)
        return filename + ".settings"

    @staticmethod
    def get_submap_settings_file(submap):
        submap_filename = Generator.get_submap_filename(submap)
        if submap_filename is not None:
            return Generator.get_settings_file(submap_filename)
        return None

    @abstractmethod
    def exec(self):
        pass

    def generate(self):
        try:
            if not self.is_generation_skipped():
                reksio.info(f"Calling {self.GENERATOR_NAME}, full output path: {self.output_path}, configuration: \n"
                            f"{self.settings}"
                            f"Input map: {self.opened_file}")
                self.exec()
            else:
                reksio.warn(f"The generation of {self.CONFIG_PATH} is skipped via settings file!")
        except Exception as e:
            reksio.critical(f"{self.GENERATOR_NAME} failed: {e}")
            print(traceback.format_exc())
        else:
            reksio.info(f"The {self.GENERATOR_NAME} finished working on {self.opened_file}.")

    @staticmethod
    @abstractmethod
    def get_arguments():
        pass

# TODO: remove this warning popup when EDGE4.1 generator will be fully tested
def _edge4_display_warning_popup():
    main_window = reksio.get_main_window()

    box = reksio.QMessageBox(main_window)
    box.setText(f"The EDGE4.1 generator is currently an EXPERIMENTAL and UNTESTED version.\n"
                f"There is no guarantee that the generated drivers will work!")
    box.setIcon(reksio.QMessageBox.Icon.Warning)
    box.addButton(reksio.QMessageBox.StandardButton.Ok)
    box.exec()
    box.clickedButton()

class DriverCSVGenerator(Generator):
    CONFIG_PATH = "EDGE_CSV"
    GENERATOR_NAME = "EDGE CSV generator"

    def exec(self):
        logging.getLogger("cheburashka").setLevel(logging.INFO)
        args = {**self.settings}
        args['output_path'] = self.output_path
        args['quiet'] = True

        import cheburashka.components.driver.driver_generator as driver_generator

        if 'style' in args and args['style'] == 'edge4':
            _edge4_display_warning_popup()

        driver_gen = driver_generator.instance(memory_map=self.opened_file, **args)
        driver_gen.execute()

    @staticmethod
    def get_arguments():
        return [
            Argument("output_path",
                     "Output path (relative to map file or absolute)",
                     'driver_edge'),
            Argument('style', "Generation style: edge2, edge3 or edge4 (experimental)", 'edge3'),
            Argument('extend_to_bus',
                     'If enabled, all registers/memories narrower than the bus ' \
                     'width will be extended to the bus width', 'False', bool)
        ]


class DriverWrapperGenerator(Generator):
    CONFIG_PATH = "DRIVER_WRAPPER"
    GENERATOR_NAME = "Driver wrapper generator"

    def exec(self):
        logging.getLogger("cheburashka").setLevel(logging.INFO)
        from cheburashka.components.driver.wrapper_generator import WrapperGenerator
        args = {**self.settings}
        args['output_path'] = self.output_path
        args['quiet'] = True

        # TODO remove it in the future
        # At one point, style 'edge' has been renamed to 'edge2'
        if 'style' in args and args['style'] == 'edge':
            raise Exception("Please change wrapper style from 'edge' to 'edge2' or 'edge3' in the settings file")

        if 'style' in args and args['style'] == 'edge4':
            _edge4_display_warning_popup()

        wrapper_gen = WrapperGenerator(mem_map_file=self.opened_file, **args)
        wrapper_gen.execute()

    @staticmethod
    def get_arguments():
        return [
            Argument("output_path",
                     "Output path (relative to map file or absolute)",
                     'driver_wrappers'),
            Argument('style', "Generation style: edge2, edge3 or edge4 (experimental)", 'edge3'),
            Argument('extend_to_bus',
                     'If enabled, all memories narrower than the bus width ' \
                     'will be extended to the bus width', 'False', bool)
        ]


class FESAGenerator(Generator):
    CONFIG_PATH = "FESA_GENERATOR"
    GENERATOR_NAME = "FESA class generator"

    def exec(self):
        logging.getLogger("cheburashka").setLevel(logging.INFO)
        from cheburashka.components.fesa.fesa_generator import FesaGenerator
        args = {**self.settings}
        args['output_path'] = self.output_path
        fesa_gen = FesaGenerator(mem_map_file=self.opened_file, quiet=True, **args)
        fesa_gen.execute()

    @staticmethod
    def get_arguments():
        return [
            Argument("output_path",
                     "Output path (relative to map file or absolute)",
                     'fesa')
        ]


class GenaGenerator(Generator):
    CONFIG_PATH = "GENA"
    GENERATOR_NAME = "Gena-Cheby generator"

    def exec(self):
        from cheby import gen_gena_regctrl, gen_gena_memmap, expand_hdl, print_vhdl
        from cheby.parser import parse_yaml
        from cheby.layout import layout_cheby

        args = {**self.settings}
        args['output_path'] = self.output_path
        args["__hdl"] = "vhdl"  # needed for gen_comment_header

        if self.submap_node is None:
            gen_options = {"regctrl": True, "memmap": True}
        else:
            gen_options = {"regctrl": False, "memmap": True}
            gen_include = self.submap_node.getAttribute("include")
            if gen_include is not None:
                # gena2cheby creates maps with "True"/"False", where we test for "true"/"false"
                gen_include.value = gen_include.value.lower()
                if gen_include.value == "false":
                    gen_options["memmap"] = True
                    gen_options["regctrl"] = True
                elif gen_include.value == "true":
                    gen_options["memmap"] = True
                    gen_options["regctrl"] = False
            else:
                # no include (default == false)
                gen_options["memmap"] = True
                gen_options["regctrl"] = True

        try:
            reksio.info(f"Generating MemMap and/or RegCtrl for {self.map_node.name}...")
            t = parse_yaml(self.opened_file)
            layout_cheby(t)

            expand_hdl.expand_hdl(t)

            memmap_h = gen_gena_memmap.gen_gena_memmap(t)

            args['output_path'] = self.output_path

            if not os.path.exists(args['output_path']):
                os.mkdir(args['output_path'])

            if gen_options["regctrl"]:
                reksio.info(f"Generating RegCtrl for {self.map_node.name}...")
                with open(os.path.join(args['output_path'], args["regctrl_filename"]), "w") as f:
                    h = gen_gena_regctrl.gen_gena_regctrl(t, args["use_common_visual"])
                    if args['generate_header']:
                        gen_comment_header(f, args)
                    print_vhdl.print_vhdl(f, h)
            if gen_options["memmap"]:
                reksio.info(f"Generating Memmap for {self.map_node.name}...")
                with open(os.path.join(args['output_path'], args["memmap_filename"]), "w") as f:
                    if args['generate_header']:
                        gen_comment_header(f, args)
                    print_vhdl.print_vhdl(f, memmap_h)
        except Exception as e:
            reksio.critical(f"RegCtrl and/or MemMap failed for {self.map_node.name}: {e}")
        else:
            reksio.info(f"RegCtrl and/or MemMap generated for {self.map_node.name} inside {self.output_path}!")

    @staticmethod
    def get_arguments():
        return [
            Argument('output_path', "Output path (relative to map file or absolute)", 'HDL'),
            Argument('use_common_visual', 'use common visual (yes/no, true/false, on/off etc)', 'False', bool),
            Argument('generate_header', "include comment, true or false", 'True', bool),
            Argument('regctrl_filename', "file name for the regctrl file generation", 'RegCtrl_${NODE_NAME}.vhd'),
            Argument('memmap_filename', "file name for the memmap file generation", 'MemMap_${NODE_NAME}.vhd')
        ]


class WBGenerator(Generator):
    CONFIG_PATH = "WB_GEN"
    GENERATOR_NAME = "Wishbone(cheby) generator"

    def exec(self):
        from cheby import expand_hdl, gen_name, gen_wbgen_hdl, print_vhdl, print_verilog
        from cheby.parser import parse_yaml
        from cheby.layout import layout_cheby
        import time

        try:
            args = {**self.settings}
            args['output_path'] = self.output_path

            if not os.path.exists(args['output_path']):
                os.mkdir(args['output_path'])

            extensions = {
                'vhdl': 'vhd',
                'verilog': 'v'
            }
            extension = extensions[args['hdl']]

            output_filename = f"{args['output_filename_no_extension']}.{extension}"

            output_file = os.path.join(self.output_path, output_filename)

            t = parse_yaml(self.opened_file)
            layout_cheby(t)
            gen_name.gen_name_memmap(t)
            expand_hdl.expand_hdl(t)
            gen_name.gen_name_memmap(t)
            h = gen_wbgen_hdl.expand_hdl(t)

            with open(output_file, 'w') as f:
                if not args['generate_header']:
                    (basename, _) = os.path.splitext(os.path.basename(output_filename))
                    c = {'vhdl': '--', 'verilog': '//'}[args['hdl']]
                    l = c[0] * 79
                    header = """{l}
            {c} Title          : Wishbone slave core for {name}
            {l}
            {c} File           : {basename}.{ext}
            {c} Author         : auto-generated by wbgen2 from {basename}.wb
            {c} Created        : {date}
            {c} Standard       : VHDL'87
            {l}
            {c} THIS FILE WAS GENERATED BY wbgen2 FROM SOURCE FILE {basename}.wb
            {c} DO NOT HAND-EDIT UNLESS IT'S ABSOLUTELY NECESSARY!
            {l}

            """
                    f.write(header.format(name=t.description, basename=basename,
                                          date=time.strftime("%a %b %d %X %Y"),
                                          c=c, l=l, ext=extension))
                print_vhdl.style = 'wbgen'
                if args["hdl"] == 'vhdl':
                    print_vhdl.print_vhdl(f, h)
                elif args["hdl"] == 'verilog':
                    print_verilog.print_verilog(f, h)
        except Exception as e:
            reksio.critical(f"Cheby (wb) hdl generation failed: {e}")
        else:
            reksio.info(f"Cheby (wb) hdl generation finished!")

    @staticmethod
    def get_arguments():
        return [
            Argument('output_path', "Output path (relative to map file or absolute)", 'HDL'),
            Argument('hdl', "HDL type, vhdl or verilog", 'vhdl', enum=['vhdl', 'verilog']),
            Argument('generate_header', "include comment, true or false", 'True', bool),
            Argument('output_filename_no_extension', "Generated file name", "${NODE_NAME}")
        ]


from cheby import expand_hdl, gen_name, print_vhdl, print_verilog, gen_hdl
from cheby.parser import parse_yaml
from cheby.layout import layout_cheby
import cheby.hdl.globals

class ChebyGenerator(Generator):
    CONFIG_PATH = "CHEBY"
    GENERATOR_NAME = "Cheby generator"

    def exec(self):
        args = {**self.settings}
        args['output_path'] = self.output_path

        root_word_endian = self.root_settings.get('word_endian')

        reksio.warn(f"Using word endian {root_word_endian} set in the top level config!")

        if not os.path.exists(args['output_path']):
            os.mkdir(args['output_path'])
        output_filename = args["output_filename"]

        output_file = os.path.join(self.output_path, output_filename)

        try:
            cheby.hdl.globals.gconfig.rst_sync = args["ff_reset"] != "async"
            cheby.layout.word_endianness = root_word_endian
            t = parse_yaml(self.opened_file)

            layout_cheby(t)
            gen_name.gen_name_memmap(t)
            expand_hdl.expand_hdl(t)
            gen_name.gen_name_memmap(t)

            address_spaces = [child for child in t.children if child.name.startswith('bar')]

            if not address_spaces:
                self._generate_hdl(t, output_file, **args)
                return

            for address_space in address_spaces:
                self._generate_hdl(address_space, f'{output_file}_{address_space.name}', **args)

        except Exception as e:
            reksio.critical(f"Cheby hdl generation failed: {e}")
        else:
            reksio.info(f"Cheby hdl generation finished!")

    def _generate_hdl(self, layout, output_file_name, **kwargs):
        kwargs["__hdl"] = kwargs["hdl"]
        h = gen_hdl.generate_hdl(layout)

        with open(output_file_name, "w") as f:
            if kwargs['generate_header']:
                gen_comment_header(f, kwargs)
            if kwargs["hdl"] == 'vhdl':
                print_vhdl.print_vhdl(f, h)
            elif kwargs["hdl"] == 'verilog':
                print_verilog.print_verilog(f, h)

    @staticmethod
    def get_arguments():
        return [
            Argument('output_path', "Output path (relative to map file or absolute)", 'HDL'),
            Argument('ff_reset', 'FF reset, sync or async', 'sync', enum=['sync', 'async']),
            Argument('word_endian', "word endian, big, little or default", 'default', enum=['big', 'little', 'default']),
            Argument('hdl', "HDL type, vhdl or verilog", 'vhdl', enum=['vhdl', 'verilog']),
            Argument('generate_header', "include comment, true or false", 'True', bool),
            Argument('output_filename', "Generated file name", "${NODE_NAME}${HDL_NAME_SUFFIX}.vhd")
        ]


class DSPGenerator(Generator):
    CONFIG_PATH = "DSP"
    GENERATOR_NAME = "DSP headers generator"

    def exec(self):
        import cheby
        import cheby.main
        from cheby import gen_gena_dsp
        from cheby.parser import parse_yaml
        from cheby.layout import layout_cheby

        try:
            output_path = self.output_path
            include_path = os.path.join(output_path, "include")

            os.makedirs(output_path, exist_ok=True)
            os.makedirs(include_path, exist_ok=True)

            t = parse_yaml(self.opened_file)
            layout_cheby(t)

            dsp_c_output_file = os.path.join(output_path, "vmeacc_%s.c" % t.name)
            dsp_h_output_file = os.path.join(include_path, "vmeacc_%s.h" % t.name)
            dsp_m_output_file = os.path.join(include_path, "MemMapDSP_%s.h" % t.name)

            args = cheby.main.decode_args()
            args.gen_gena_dsp_c = dsp_c_output_file
            args.gen_gena_dsp_h = dsp_h_output_file
            args.gen_gena_dsp_map = dsp_m_output_file

            cheby.main.handle_file(args, self.opened_file)

        except Exception as e:
            reksio.critical(f"DSP headers generation failed: {e}")
        else:
            reksio.info(f"DSP headers generation finished!")

    @staticmethod
    def get_arguments():
        return [
            Argument('output_path', "Output path (relative to map file or absolute)", 'DSP'),
        ]


class HDLGenerator(Generator):
    CONFIG_PATH = "HDL_GEN"
    GENERATOR_NAME = "Generic HDL generator"
    GENERATORS = {
        'cheby': ChebyGenerator,
        'gena': GenaGenerator,
        'wb': WBGenerator
    }

    def get_hdl_generator(self):
        return self.GENERATORS.get(self.settings['generator'])

    def exec(self):
        cls = self.get_hdl_generator()
        reksio.info(f"Using generator (as in settings file): {cls.GENERATOR_NAME}")
        generator = cls(self.main_window, self.submap_node)
        generator.generate()

    @staticmethod
    def get_arguments():
        return [
            Argument('generator',
                     "Generator for HDL: cheby, gena, wb",
                     'cheby',
                     enum=['cheby', 'gena', 'wb'])
        ]


class RecursiveHDLGenerator(HDLGenerator):
    GENERATOR_NAME = "Recursive generic HDL generator"

    def exec(self):
        # call for top item
        super().exec()
        # call for its submaps
        submaps = self.get_submap_nodes()
        for submap in submaps:
            submap_file = self.get_submap_filename(submap)
            if submap_file is None:
                continue
            hdl_gen = HDLGenerator(self.main_window, submap)
            hdl_gen.generate()


class ConstsGenerator(Generator):
    CONFIG_PATH = "CONST_GEN"
    GENERATOR_NAME = "Constants generator"

    def exec(self):
        from cheby import expand_hdl, gen_name, print_consts
        from cheby.parser import parse_yaml
        from cheby.layout import layout_cheby


        args = {**self.settings}
        args['output_path'] = self.output_path

        t = parse_yaml(self.opened_file)

        layout_cheby(t)
        gen_name.gen_name_memmap(t)
        expand_hdl.expand_hdl(t)
        gen_name.gen_name_memmap(t)

        if not os.path.exists(args['output_path']):
            os.mkdir(args['output_path'])

        style_extension = {
            'vhdl': 'vhd',
            'verilog': 'v',
            'h': 'h',
            'vhdl-ohwr': 'vhd',
            'vhdl-orig': 'vhd'
        }
        style_suffix = {
            'vhdl': '_Consts',
            'vhdl-orig': '_Consts',
            'vhdl-ohwr': '_consts_pkg'
        }

        output_filename = f"{args['output_filename_no_extension']}{style_suffix.get(args['style'], '')}.{style_extension.get(args['style'], 'ext')}"
        output_file = os.path.join(self.output_path, output_filename)

        with open(output_file, "w") as f:
            print_consts.pconsts_cheby(f, t, args["style"])

    @staticmethod
    def get_arguments():
        return [
            Argument('output_path', "Output path (relative to map file or absolute)", 'HDL'),
            Argument('style', "vhdl, verilog, h, vhdl-ohwr, vhdl-orig", "vhdl-ohwr",
                     enum=['vhdl', 'verilog', 'h', 'vhdl-ohwr', 'vhdl-orig']),
            Argument('output_filename_no_extension', "Generated file name", "${NODE_NAME}${HDL_NAME_SUFFIX}")
        ]


class DocGenerator(Generator):
    CONFIG_PATH = "DOCS_GEN"
    GENERATOR_NAME = "Docs (cheby) generator"

    def exec(self):
        from cheby import expand_hdl, gen_name, print_html, print_markdown
        from cheby.parser import parse_yaml
        from cheby.layout import layout_cheby

        args = {**self.settings}
        args['output_path'] = self.output_path

        t = parse_yaml(self.opened_file)

        layout_cheby(t)
        gen_name.gen_name_memmap(t)
        expand_hdl.expand_hdl(t)
        gen_name.gen_name_memmap(t)

        if not os.path.exists(args['output_path']):
            os.mkdir(args['output_path'])

        style_extension = {
            'html': 'html',
            'md': 'md'
        }

        output_filename = f"{args['output_filename_no_extension']}.{style_extension[args['style']]}"

        output_file = os.path.join(self.output_path, output_filename)

        with open(output_file, "w") as f:
            if args["style"] == "html":
                print_html.pprint(f, t)
            elif args["style"] == "md":
                print_markdown.print_markdown(f, t)

    @staticmethod
    def get_arguments():
        return [
            Argument('output_path', "Output path (relative to map file or absolute)", 'docs'),
            Argument('style', "html or md", 'html', enum=['html', 'md']),
            Argument('output_filename_no_extension', "Generated file name", "${NODE_NAME}")
        ]


def driver(main_window, node=None):
    driver_gen = DriverCSVGenerator(main_window, node)
    driver_gen.generate()


def driver_node(node):
    driver(reksio.get_main_window(), node)


def wrapper(main_window, node=None):
    wrapper_gen = DriverWrapperGenerator(main_window, node)
    wrapper_gen.generate()


def wrapper_node(node):
    wrapper(reksio.get_main_window(), node)


def fesa(main_window, node=None):
    fesa_gen = FESAGenerator(main_window, node)
    fesa_gen.generate()


def fesa_node(node):
    fesa(reksio.get_main_window(), node)


def cheby_hdl(main_window, node=None):
    cheby_gen = ChebyGenerator(main_window, node)
    cheby_gen.generate()


def cheby_hdl_node(node):
    cheby_hdl(reksio.get_main_window(), node)


def gena_regctrl_memmap_node(node):
    gena_regctrl_memmap(reksio.get_main_window(), node)


def gena_regctrl_memmap(main_window, node=None):
    gena_gen = GenaGenerator(main_window, node)
    gena_gen.generate()


def hdl_gen_node(node):
    hdl_gen(reksio.get_main_window(), node)


def hdl_gen(main_window, node=None):
    hdl_gen = HDLGenerator(main_window, node)
    hdl_gen.generate()


def wb_gen_node(node):
    wb_gen(reksio.get_main_window(), node)


def wb_gen(main_window, node=None):
    wb_gen = WBGenerator(main_window, node)
    wb_gen.generate()


def recursive_hdl_gen_node(node):
    recursive_hdl_gen(reksio.get_main_window(), node)


def recursive_hdl_gen(main_window, node=None):
    rec_hdl_gen = RecursiveHDLGenerator(main_window, node)
    rec_hdl_gen.generate()


def gen_comment_header(f, args):
    import getpass
    import time
    from cheby import __version__ as cheby_version
    c = {'vhdl': '--', 'verilog': '//'}[args["__hdl"]]
    f.write("{} Do not edit.  Generated on {date} by {user}\n".format(
        c, date=time.strftime("%a %b %d %X %Y"), user=getpass.getuser()))
    f.write("{} With Cheby {}\n".format(
        c, cheby_version))
    f.write("\n")


def gen_consts_node(node):
    gen_consts(reksio.get_main_window(), node)


def gen_consts(main_window, node=None):
    const_gen = ConstsGenerator(main_window, node)
    const_gen.generate()


def gen_docs_node(node):
    gen_docs(reksio.get_main_window(), node)


def gen_docs(main_window, node=None):
    doc_gen = DocGenerator(main_window, node)
    doc_gen.generate()


def recursive_hdl_docs_consts(main_window, node=None):
    recursive_hdl_gen(main_window, node)
    gen_consts(main_window, node)
    gen_docs(main_window, node)


def dsp_headers(main_window, node=None):
    dsp_gen = DSPGenerator(main_window, node)
    dsp_gen.generate()


def dsp_headers_node(node):
    dsp_headers(reksio.get_main_window(), node)
